// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/*
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Drawing;

using AM.Reporting.Utils;

using System.Windows.Forms;

#endregion

#nullable enable

namespace AM.Reporting.Table
{
    /// <summary>
    /// Specifies how to display the duplicate values.
    /// </summary>
    public enum CellDuplicates
    {
        /// <summary>
        /// The <b>TableCell</b> can show duplicate values.
        /// </summary>
        Show,

        /// <summary>
        /// The <b>TableCell</b> with duplicate value will be shown but with no text.
        /// </summary>
        Clear,

        /// <summary>
        /// Several <b>TableCell</b> objects with the same value will be merged into one object.
        /// </summary>
        Merge,

        /// <summary>
        /// Several <b>TableCell</b> objects with the same non-empty value will be merged into one object.
        /// </summary>
        MergeNonEmpty
    }

    /// <summary>
    /// Represents a table cell.
    /// </summary>
    /// <remarks>
    /// Use <see cref="ColSpan"/>, <see cref="RowSpan"/> properties to set the cell's
    /// column and row spans. To put an object inside the cell, use its <see cref="Objects"/> property:
    /// <code>
    /// TableCell cell1;
    /// PictureObject picture1 = new PictureObject();
    /// picture1.Bounds = new RectangleF(0, 0, 32, 32);
    /// picture1.Name = "Picture1";
    /// cell1.Objects.Add(picture1);
    /// </code>
    /// </remarks>
    public partial class TableCell : TextObject, IParent
    {
        #region Fields

        private int colSpan;
        private int rowSpan;
        private ReportComponentCollection objects;
        private int savedOriginalObjectsCount;

        #endregion

        #region Properties

        /// <summary>
        /// Gets a collection of objects contained in this cell.
        /// </summary>
        [Browsable (false)]
        public ReportComponentCollection Objects
        {
            get
            {
                if (CellData != null)
                {
                    return CellData.Objects;
                }

                return objects;
            }
        }

        /// <summary>
        /// Gets or sets the column span for this cell.
        /// </summary>
        [DefaultValue (1)]
        [Category ("Appearance")]
        public int ColSpan
        {
            get
            {
                if (CellData != null)
                {
                    return CellData.ColSpan;
                }

                return colSpan;
            }
            set
            {
                if (CellData != null)
                {
                    CellData.ColSpan = value;
                }

                colSpan = value;
            }
        }

        /// <summary>
        /// Gets or sets the row span for this cell.
        /// </summary>
        [DefaultValue (1)]
        [Category ("Appearance")]
        public int RowSpan
        {
            get
            {
                if (CellData != null)
                {
                    return CellData.RowSpan;
                }

                return rowSpan;
            }
            set
            {
                if (CellData != null)
                {
                    CellData.RowSpan = value;
                }

                rowSpan = value;
            }
        }

        /// <inheritdoc/>
        public override string Text
        {
            get
            {
                if (CellData != null)
                {
                    return CellData.Text;
                }

                return base.Text;
            }
            set
            {
                if (CellData != null)
                {
                    CellData.Text = value;
                }

                base.Text = value;
            }
        }

        /// <summary>
        /// Gets or sets a value that determines how to display duplicate values in the cells of the same group.
        /// </summary>
        [DefaultValue (CellDuplicates.Show)]
        [Category ("Behavior")]
        public CellDuplicates CellDuplicates { get; set; }

        /// <summary>
        /// Ges or sets data associated with this cell. For internal use only.
        /// </summary>
        [Browsable (false)]
        public TableCellData CellData { get; set; }

        /// <summary>
        /// Gets the address of this cell.
        /// </summary>
        [Browsable (false)]
        public Point Address => CellData == null ? new Point() : CellData.Address;

        /// <summary>
        /// This property is not relevant to this class.
        /// </summary>
        [Browsable (false)]
        public override float Width
        {
            get
            {
                if (CellData != null)
                {
                    return CellData.Width;
                }

                return base.Width;
            }
            set => base.Width = value;
        }

        /// <summary>
        /// This property is not relevant to this class.
        /// </summary>
        [Browsable (false)]
        public override float Height
        {
            get
            {
                if (CellData != null)
                {
                    return CellData.Height;
                }

                return base.Height;
            }
            set => base.Height = value;
        }

        /// <inheritdoc/>
        public override float AbsLeft => (Table != null) ? Table.AbsLeft + Left : base.AbsLeft;

        /// <inheritdoc/>
        public override float AbsTop => (Table != null) ? Table.AbsTop + Top : base.AbsTop;

        /// <summary>
        /// Gets the <b>TableBase</b> object which this cell belongs to.
        /// </summary>
        [Browsable (false)]
        public TableBase Table => Parent == null ? null : Parent.Parent as TableBase;

        #endregion

        #region Public Methods

        /// <inheritdoc/>
        public override void Assign (Base source)
        {
            base.Assign (source);

            var src = source as TableCell;
            ColSpan = src.ColSpan;
            RowSpan = src.RowSpan;
            CellDuplicates = src.CellDuplicates;
        }

        /// <summary>
        /// Creates the exact copy of this cell.
        /// </summary>
        /// <returns>The copy of this cell.</returns>
        public TableCell Clone()
        {
            var cell = new TableCell();
            cell.AssignAll (this);
            return cell;
        }

        /// <summary>
        /// Determines if two cells have identical settings.
        /// </summary>
        /// <param name="cell">Cell to compare with.</param>
        /// <returns><b>true</b> if cells are equal.</returns>
        public bool Equals (TableCell cell)
        {
            // do not override exising Equals method. It is used to compare elements in a list,
            // and will cause problems in the designer.
            return cell != null &&
                   Fill.Equals (cell.Fill) &&
                   TextFill.Equals (cell.TextFill) &&
                   HorizontalAlign == cell.HorizontalAlign &&
                   VerticalAlign == cell.VerticalAlign &&
                   Border.Equals (cell.Border) &&
                   Font.Equals (cell.Font) &&
                   Formats.Equals (cell.Formats) &&
                   Highlight.Equals (cell.Highlight) &&
                   Restrictions == cell.Restrictions &&
                   Hyperlink.Equals (cell.Hyperlink) &&
                   Padding == cell.Padding &&
                   AllowExpressions == cell.AllowExpressions &&
                   Brackets == cell.Brackets &&
                   HideZeros == cell.HideZeros &&
                   HideValue == cell.HideValue &&
                   Angle == cell.Angle &&
                   RightToLeft == cell.RightToLeft &&
                   WordWrap == cell.WordWrap &&
                   Underlines == cell.Underlines &&
                   Trimming == cell.Trimming &&
                   FontWidthRatio == cell.FontWidthRatio &&
                   FirstTabOffset == cell.FirstTabOffset &&
                   ParagraphOffset == cell.ParagraphOffset &&
                   TabWidth == cell.TabWidth &&
                   Clip == cell.Clip &&
                   Wysiwyg == cell.Wysiwyg &&
                   LineHeight == cell.LineHeight &&
                   Style == cell.Style &&
                   EvenStyle == cell.EvenStyle &&
                   HoverStyle == cell.HoverStyle &&
                   HasHtmlTags == cell.HasHtmlTags &&
                   NullValue == cell.NullValue &&
                   ProcessAt == cell.ProcessAt &&
                   Printable == cell.Printable &&
                   Exportable == cell.Exportable &&
                   CellDuplicates == cell.CellDuplicates &&

                   // events
                   BeforePrintEvent == cell.BeforePrintEvent &&
                   AfterPrintEvent == cell.AfterPrintEvent &&
                   AfterDataEvent == cell.AfterDataEvent
                   &&
                   Cursor == cell.Cursor &&
                   ClickEvent == cell.ClickEvent &&
                   MouseDownEvent == cell.MouseDownEvent &&
                   MouseMoveEvent == cell.MouseMoveEvent &&
                   MouseUpEvent == cell.MouseUpEvent &&
                   MouseEnterEvent == cell.MouseEnterEvent &&
                   MouseLeaveEvent == cell.MouseLeaveEvent
                ;
        }

        /// <inheritdoc/>
        public override void Serialize (ReportWriter writer)
        {
            var c = writer.DiffObject as TableCell;
            base.Serialize (writer);

            if (ColSpan != c.ColSpan)
            {
                writer.WriteInt ("ColSpan", ColSpan);
            }

            if (RowSpan != c.RowSpan)
            {
                writer.WriteInt ("RowSpan", RowSpan);
            }

            if (CellDuplicates != c.CellDuplicates)
            {
                writer.WriteValue ("CellDuplicates", CellDuplicates);
            }
        }

        /// <summary>
        /// Changes the cell's style.
        /// </summary>
        /// <param name="style">The new style.</param>
        /// <remarks>
        /// Each cell in a dynamic table object (or in a matrix) has associated style.
        /// Several cells may share one style. If you try to change the cell's appearance directly
        /// (like setting cell.TextColor), it may affect other cells in the table.
        /// To change the single cell, use this method.
        /// </remarks>
        public void SetStyle (TableCell style)
        {
            CellData.SetStyle (style);
        }

        #endregion

        #region Report Engine

        /// <inheritdoc/>
        public override string[] GetExpressions()
        {
            List<string> expressions = new List<string>();
            expressions.AddRange (base.GetExpressions());

            if (Objects != null)
            {
                foreach (ReportComponentBase c in Objects)
                {
                    expressions.AddRange (c.GetExpressions());
                }
            }

            return expressions.ToArray();
        }

        /// <inheritdoc/>
        public override void SaveState()
        {
            base.SaveState();
            OnBeforePrint (EventArgs.Empty);

            if (Objects != null)
            {
                savedOriginalObjectsCount = Objects.Count;

                foreach (ReportComponentBase c in Objects)
                {
                    c.SaveState();
                    c.OnBeforePrint (EventArgs.Empty);
                }
            }
        }

        /// <inheritdoc/>
        public override void RestoreState()
        {
            OnAfterPrint (EventArgs.Empty);
            base.RestoreState();

            if (Objects != null)
            {
                while (Objects.Count > savedOriginalObjectsCount)
                {
                    Objects[Objects.Count - 1].Dispose();
                }

                for (var i = 0; i < Objects.Count; i++)
                {
                    var c = Objects[i];
                    c.OnAfterPrint (EventArgs.Empty);
                    c.RestoreState();
                }
            }
        }

        /// <inheritdoc/>
        public override void GetData()
        {
            base.GetData();
            if (Table != null && Table.IsInsideSpan (this))
            {
                Text = "";
            }

            if (Objects != null)
            {
                for (var i = 0; i < savedOriginalObjectsCount; i++)
                {
                    var c = Objects[i];
                    c.GetData();
                    c.OnAfterData();
                }
            }

            OnAfterData();
        }

        #endregion

        #region IParent Members

        /// <inheritdoc/>
        public bool CanContain (Base child)
        {
            var insideSpan = false;
            if (Table != null)
            {
                insideSpan = Table.IsInsideSpan (this);
            }

            return !insideSpan && child is ReportComponentBase and not BandBase && child != Table;
        }

        /// <inheritdoc/>
        public void GetChildObjects (ObjectCollection list)
        {
            if (Objects != null)
            {
                foreach (ReportComponentBase obj in Objects)
                {
                    list.Add (obj);
                }
            }
        }

        /// <inheritdoc/>
        public void AddChild (Base child)
        {
            if (child is ReportComponentBase @base)
            {
                if (Objects == null)
                {
                    objects = new ReportComponentCollection (this);
                    if (CellData != null)
                    {
                        CellData.Objects = objects;
                    }
                }

                Objects.Add (@base);

                if (@base is TableBase tableBase)
                {
                    tableBase.PrintOnParent = true;
                }
            }
        }

        /// <inheritdoc/>
        public void RemoveChild (Base child)
        {
            if (child is ReportComponentBase @base)
            {
                Objects.Remove (@base);
            }
        }

        /// <inheritdoc/>
        public int GetChildOrder (Base child)
        {
            if (child is ReportComponentBase @base)
            {
                return Objects.IndexOf (@base);
            }

            return 0;
        }

        /// <inheritdoc/>
        public void SetChildOrder (Base child, int order)
        {
            if (child is ReportComponentBase @base)
            {
                var oldOrder = @base.ZOrder;
                if (oldOrder != -1 && order != -1 && oldOrder != order)
                {
                    if (order > Objects.Count)
                    {
                        order = Objects.Count;
                    }

                    if (oldOrder <= order)
                    {
                        order--;
                    }

                    Objects.Remove (@base);
                    Objects.Insert (order, @base);
                    UpdateLayout (0, 0);
                }
            }
        }

        /// <inheritdoc/>
        public void UpdateLayout (float dx, float dy)
        {
            if (CellData != null)
            {
                CellData.UpdateLayout (dx, dy);
            }
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="TableCell"/> class.
        /// </summary>
        public TableCell()
        {
            colSpan = 1;
            rowSpan = 1;
            Padding = new Padding (2, 1, 2, 1);
            SetFlags (Flags.CanDelete | Flags.CanCopy | Flags.CanMove | Flags.CanResize |
                      Flags.CanChangeParent | Flags.CanDraw | Flags.CanWriteBounds, false);
            BaseName = "Cell";
        }
    }
}
